Skip to content

chore(sync): merge upstream TanStack/virtual main (RIG-9206) - #13

Open
rigel-mintaka wants to merge 47 commits into
rigel-releasefrom
virtualsync/rig-9206-merge-upstream-main-54a3f7d3cc32
Open

rigel-mintaka wants to merge 47 commits into
rigel-releasefrom
virtualsync/rig-9206-merge-upstream-main-54a3f7d3cc32

Conversation

@rigel-mintaka

@rigel-mintaka rigel-mintaka commented Nov 12, 2026 •

Copy link
Copy Markdown
Collaborator

🎯 Changes

Merge upstream TanStack/virtual main into the RigelBuild fork to keep its upstream baseline current. This merge had one conflict in packages/solid-virtual/package.json; we kept @rigelbuild/solid-virtual at 3.0.0-rc.0 and retained the public publishConfig required by the fork's release workflow.

✅ Checklist

  • The upstream changes merge into the fork default branch, main.
  • Workflow contract tests pass.

🚀 Release Impact

This sync preserves the existing published package identity and public publish configuration. It does not change the release path.

Refs RIG-9206

Co-authored-by: Matt Wilkinson matt@rigel.build

piecyk and others added 30 commits October 10, 2026 19:49
… the scroll state (TanStack#1324)

Finishes TanStack#1307 on top of TanStack#1319's hold: the step-5 command stalls, iOS 27 commands issued mid-fling (wait for the fling; a drag, second flick or tap cancels, read from scroll events since iOS sends no touch events for a touch on a fling), and the behaviour-neutral scroll-state refactor (P1–P4). Device checks on iOS 27/26.5, desktop Safari, Chromium and Firefox; e2e for the command-retire cases.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…TanStack#1325)

Moves the iOS touch gesture state out of the Virtualizer into an internal IosGesture class. The late-fling window is now a gesture phase. A waiting command keeps its fling samples. One fix: a scroll-element change no longer carries over a pending late-fling window.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… clamp (TanStack#1329)

* fix(virtual-core): keep an instant scroll command through a transient clamp

Rows measured before an instant scrollToEnd() reports back can shrink the
sizer below the written position and then grow it again in the same task.
The browser clamps the write to the smaller max, and the scroll event
reports that position: neither the write target nor the current max, so it
was classified as external and the command was retired short of the end.

resizeItem now follows the scroll max while a command's instant write is
unreported (element max read once, then each delta, no measurement
rebuild), and classifyScrollEvent reads an event between the lowest max and
the target as clampedReadBack, so reconcile retargets to the end.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(virtual-core): count a pre-shrunk max and rewrite after a transient clamp

Review follow-ups for the transient-clamp read-back:

- The element max read at the first resize after the write counts toward
  `lowestMax`: the sizer can already be below the target when that resize
  arrives.
- After such a clamp, an index or end command moves `lastTargetOffset` to
  the clamped position, so reconcile writes the target again even when the
  layout came back to the same end. Only for a clamp below the element's
  current max; the clamp at the current max (TanStack#1310) is unchanged.
- The max is followed only while `_awaitingCommandReadBack()` holds (not
  under an iOS dragging finger) and only with one lane, where a row's delta
  is the total's.

Scroll-trace snapshots are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(virtual-core): match a transient clamp to a max the layout took

A clamped read-back below the element's current max was accepted anywhere
between the lowest tracked max and the target, so a user scroll that
landed in that range before the read-back kept the command. The browser
clamps to a max the sizer actually had, so `pendingWrite` now keeps each
tracked max below the target (`lowMaxes`) and the classifier requires the
offset to be within the read-back tolerance of one of them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(virtual-core): record a transient max below zero at 0

A row that shrinks the tracked max below zero leaves the element clamped at
0, not at the negative value, so a read-back at 0 after the sizer grew back
matched no recorded max and retired the command. `lowMaxes` now records the
reachable value (the element's 0, which is the hold in layout coordinates);
the raw max still carries the next delta.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…TanStack#1331)

* docs(examples): rebuild the React chat example as a ChatGPT-style layout

Replace the debug toolbar with a sidebar (new chat, chat history) and a
message pane with a composer. Data comes from an in-memory mock API through
TanStack Query: chats via useQuery, messages via useInfiniteQuery with older
pages prepended, and sends via useMutation. Assistant replies are generated
and streamed word by word into the query cache, so the example needs no
server or API key.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs(examples): fix clipped sidebar rows and small icons in the chat example

The chat list rows shrank below their text height once the list overflowed,
which cut off the text and the selected-row highlight on phones. Keep them
at their natural height, and replace the text-glyph icons with 20px SVG
icons in 40px buttons.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs(examples): jump to the end after a send in the chat example

The empty assistant reply is added in the same render as the user message,
so the last message was never the user's and scrollToEnd() never ran. Watch
the latest user message instead, and skip the first render.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs(examples): hide the jump button when the chat opens at the end

React's onScroll runs before the virtualizer reads the scroll event, so
isAtEnd() there saw the previous position and the button stayed visible
after the list landed at the end. Update the at-end state in the
virtualizer's onChange instead.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs(examples): simplify the chat example

- Seed 5 chats instead of 25, and generate replies from one sentence bank
  (no lists or code blocks), so the mock API and rendering are shorter.
- Add the streamed reply with its first words, which removes the typing
  indicator and fixes an empty reply left behind by an early Stop.
- Scroll to the end in one layout effect keyed on the latest user message,
  replacing the initial-scroll state, the seen-message ref and the
  history-load timer.
- Let the composer grow with field-sizing: content instead of JavaScript.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs(examples): add prompt hints to the empty chat

Show three prompts under the composer when no chat is open. A tap sends the
prompt as the first message, so a new chat and a streamed reply start in one
step.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs(examples): link the chat blog post and add icons to the prompt hints

- Answer "How do I virtualize a chat list?" with a fixed reply that links
  Tanner Linsley's "Chat UIs Are Lists Until They Aren't", and render links
  in assistant messages.
- Add a colored Feather icon to each prompt hint.
- Rename the seeded "Plan a weekend in Kraków" chat to Rzeszów.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs(examples): keep the chat opened while a send is in flight

The send selects its chat when it finishes, about 100 ms later. Opening
another chat in that time was undone. Select the sent-to chat only when the
selection has not changed since the send started.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs(examples): load older messages after a direct jump to the top

The scroll handler returned early when isAtEnd() was true. React's onScroll
runs before the virtualizer reads the event, so after one jump from the end
to the top it still saw the old position, skipped the load and did not
record scrollTop. Drop the check: scrolling up near the top already gates
the load.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
dgesteves and others added 17 commits October 26, 2026 12:51
…on Android too (TanStack#1347)

Chrome on Android carries a fling on from a programmatic write, so a
scrollToIndex / scrollToOffset / scrollToEnd issued mid-fling was written
and then overshot (Pixel 6, Android 16, Chrome 156: ~3,200px past the
target, 3 runs of 3). iOS 27 already stops the fling before such a command
(TanStack#1332); this does it everywhere a write does not end the fling, which is
everywhere but iOS 26 and earlier.

- IosGesture opens the post-touch tail on every platform. Corrections are
  still deferred through it on iOS only; `_canWriteScroll` and
  `_awaitingCommandReadBack` keep their isIOSWebKit() gates.
- `_startScroll` stops the fling when momentumOverridesWrites():
  !isIOSWebKit() || iosMomentumOverridesWrites().
- `_stopMomentum` puts the overflow back in the same task when hiding it
  changed the element's cross-axis client size: a classic scrollbar (a
  touchscreen laptop) would vanish and reflow the content for two frames.
  The command then waits for the tail, as without a style.

The web trace snapshots move for seeds with touch steps: a command inside
a drag's tail now goes through the stop, which in the simulator (no style)
falls back to the tail, as on ios27.
Co-authored-by: Matt Wilkinson <matt@rigel.build>
@rigel-mintaka rigel-mintaka changed the title fix(virtual-core): keep lanes aligned after changing lanes with measureElement (#1302) chore(sync): merge upstream TanStack/virtual main (RIG-9206) Nov 12, 2026
@linear-code

linear-code Bot commented Nov 12, 2026

Copy link
Copy Markdown

RIG-9206

@github-actions

Copy link
Copy Markdown

🚀 Changeset Version Preview

1 package(s) bumped directly, 8 bumped as dependents.

🟩 Patch bumps

Package Version Reason
@tanstack/virtual-core 3.19.11 → 3.19.12 Changeset
@rigelbuild/solid-virtual 3.0.0-rc.0 → 3.0.0 Dependent
@tanstack/angular-virtual 6.1.12 → 6.1.13 Dependent
@tanstack/lit-virtual 3.15.3 → 3.15.4 Dependent
@tanstack/marko-virtual 3.16.12 → 3.16.13 Dependent
@tanstack/react-virtual 3.14.26 → 3.14.27 Dependent
@tanstack/svelte-virtual 3.13.52 → 3.13.53 Dependent
@tanstack/virtual-benchmarks 0.0.29 → 0.0.30 Dependent
@tanstack/vue-virtual 3.13.52 → 3.13.53 Dependent

@rigel-mintaka

Copy link
Copy Markdown
Collaborator Author

Review round 1

One low, accepted: the CHANGELOG keeps upstream @TanStack entries. That drift predates this merge, and editing upstream entries would cause conflicts on every sync.

The conflict resolution adds and drops nothing beyond the fork fields. The fork workflows and hk.pkl are unchanged.

All-clear at bd52bbf. The gate ran over SSH on mattpc-wsl.

@rigel-mintaka
rigel-mintaka marked this pull request as ready for review November 12, 2026 09:00

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.